// move class  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Move class
chess_spider.Move = function( game, prv, move_no, color, move, level, pre_comment, comment, mode, branch_id ) {

	// incorrectly filtered pgn checks: check for $
	if( chess_spider.util.getFile(move) == '$' ) this.err( "illegal move: move_no="+move_no+", color="+color+", move="+move );

	// game
	this.seq = chess_spider.Move.nextSeq++;
	this.game = game;
	this.prv = prv;
	this.move_no = parseInt(move_no);
	this.color = color;
	this.move = move;
	this.level = parseInt(level);

	// maintain double linked list
	if( this.prv ) {
		this.nxt = this.prv.nxt;
		this.prv.nxt = this;
		if( this.nxt ) this.nxt.prv = this;
	}
	else	this.nxt = null;

	// parent/children
	this.pnt = this.prv;			// to be determined with more accuracy later
	this.branch = 0;
	this.branch_arr = new Array();

	// pstn
	// todo: test against root
	this.pstn = (this.level>0) ? this.prv.pstn : chess_spider.params.orig_pstn;

	// if init, done
	if( this.level == 0 ) {
		this.id = 'G'+this.game.idx+'B'+ (parseInt(game.first_mv.substr(0,1))-1) + ((game.first_mv.substr(1,1)=='w')?'b':'w');
		this.pstn = this.game.pstn;
		chess_spider.state.moves[this.id] = this;
		return;
	}

	// if we have branch_id, pnt/branch solved for us
	if( branch_id ) {

		this.orig_branch_id = branch_id;

		// if this branch_id is different than prv.branch_id then figure out parent
		// note: this is true because the moved are ordered
		if( branch_id != this.prv.branch_id() ) {

			// get the parent id and branch (_no) from the branch_id
			var branch_head_id = 'G' + game.idx
				+ 'B' + (( branch_id.indexOf(':')>0 ) ? branch_id.match(/^(.*):/)[1] : '') 
				+ 'M' + branch_id.match(/(^|:)([^:]*).$/)[2];
			var branch_head = chess_spider.state.moves[branch_head_id];
			this.branch = parseInt( branch_id.substr(-1) );

			// if no branch in branch_head, then branch_head is parent, and create branch in branch_head
			if( !branch_head.branch_arr[this.branch] ) {
				this.pnt.branch_arr[this.branch] = this;
				this.pnt = branch_head;
			}

			// otherwise, follow to end, which should be parent
			else{
				// search down branch for previous move
				for( var sibling = branch_head.branch_arr[this.branch]; sibling; sibling = sibling.nxt ) {
					// if different branch_id, skip
					if( sibling.branch_id() != branch_id ) continue;
					// if previous to this move
					if( sibling.move_no==this.move_no && sibling.color=='w' && this.color=='b'
					||  sibling.move_no==this.move_no-1 && sibling.color=='b' && this.color=='w' ) break;
				}
				// if null, foobar
				if( !sibling ) throw( 'unable to find next move for '+this.id );
				this.pnt = sibling;
			}


			// pnt pstn
			this.pstn = this.pnt.pstn;
		}

		// if same, then prv == pnt, as default
		else {
			this.pnt = this.prv;
			this.branch = this.prv.branch;
		}
		null;
	}

	// no branch_id, we solve it ourselves
	else {
		// we're different branch
		// theorem 1: if we are the same level and successive move, then in same branch as parent
		if( this.level == this.prv.level && this.prv.isNextMove(this) ) {
			this.pnt = this.prv;
			this.branch = this.pnt.branch;
			null;
		}

		// theorem 2: if we are first instance of lower branch or same level with small move_no, we are the head node, and the previous node is the parent
		else if( this.level == this.prv.level+1 || this.level == this.prv.level && this.isLowerMove(this.prv) ) {
			
			// find parent: step 1: move back through time until first instance of higher level
			for( this.pnt = this.prv; this.pnt.level >= this.level; this.pnt = this.pnt.prv );
			// step 2: move back through time until preceding move
			for( this.pnt; !this.pnt.isNextMove(this); this.pnt = this.pnt.prv );

			// set link, since we're branch head
			this.pnt.branch_arr.push( this );
			this.branch = this.pnt.branch_arr.indexOf( this );

			// get pstn
			this.pstn = this.pnt.pstn;
		}

		// theorum 3: rolling back from branch - pnt is logical continuation, not branch head
		else if( this.level < this.prv.level ) {

			// find parent: go back through time until previous move at same level
			for( this.pnt = this.prv; this.pnt.level > this.level; this.pnt = this.pnt.prv );
			
			// step 2: move back through time until preceding move
			for( this.pnt; !this.pnt.isNextMove(this); this.pnt = this.pnt.prv );
			this.branch = this.pnt.branch;

			// get pstn
			this.pstn = this.pnt.pstn;
		}

		// theorem 3: all other instances are invalid
		else	this.err( "parent/branch logical inconsistency" );
	}

	// we have enough for id
	this.id = this.genId();
	if( this.id == undefined ) this.err( 'undefined id' );
	//-console.log( this.move_no + this.color + ' move=' + this.move + ', id=' + this.id + ', pnt='+this.pnt.id );
	chess_spider.state.moves[this.id] = this;

	// move itself
	this.nar = 0;
	this.finish = 0;
	this.piece = null;
	this.color = color;
	this.pre_comment = pre_comment;
	this.upre_comment = null;
	this.comment = comment;
	this.ucomment = null;
	this.mode = mode;


	// get move params - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	// backfill move if not set
	if( move == undefined || move == null ) move = this.move;

	//-alert( 'move1( ' + id + ', ' + color + ', ' + move + '), this.next_color = '+color  );

	// move vars
	this.check, this.mate, this.promotion, this.capture, this.castle, this.file, this.rank, this.src_sq, this.tgt_sq = null; 

	// save move
	this.move = move;

	// check
	if( move.substr(-1,1) == '+' ) {
		this.check = true;
		move = move.substr(0,move.length-1);
		//-alert( "check: move = " + move );
	}
	else	this.check = false;

	// mate
	if( move.substr(-1,1) == '#' ) {
		this.mate = true;
		move = move.substr(0,move.length-1);
	}
	else	this.mate = false;

	// castling - put into K move format
	if( this.move == 'O-O' ) {
		this.castle = move;
		move = 'Ke'+this.backrank()+'-g'+this.backrank();
	}
	else if( this.move == 'O-O-O' ) {
		this.castle = move;
		move = 'Ke'+this.backrank()+'-c'+this.backrank();
	}

	// promotion
	if( move.substr(-2,1) == '=' ) {
		this.promotion = move.substr(-1,1);
		move = move.substr(0,move.length-2);
	}
	else	this.promotion = null;

	// capture
	if( move.indexOf('x') > 0 ) {
		this.capture = true;
		move = move.replace(/x/,'');
		//-alert( 'capture: move = '+move );
	}
	else	this.capture = false;

	// move (indicated by '-') - get rid of it
	if( move.indexOf('-') > 0 ) {
		move = move.replace(/-/,'');
		//-alert( 'capture: move = '+move );
	}

	// non-pawn piece
	var match_arr = move.match( /(P|R|N|B|Q|K)(.*)/ );
	if( match_arr && match_arr[1] ) {
		this.piece = match_arr[1].toLowerCase();
		move = match_arr[2];
	}

	// pawn or finish
	if( !this.piece ) {

		// pawn if move begins with a-h
		if(  /^[a-h]/.test(move) ) this.piece = 'p';

		// check if finish - note that the dash is removed!
		else if( move == '01' ) this.finish = 1
		else if( move == '10' ) this.finish = 2
		else if( move == '1/21/2' ) this.finish = 3
		
		// unknown piece
		else this.err( "uknown piece" );
	}

	// no piece, finish
	if( !this.piece && this.finish ) return;

	var colorPiece = ( this.color == 'w' ) ? this.piece.toUpperCase() : this.piece.toLowerCase();

	// file
	var match_arr = move.match( /([a-h])([1-8]|)([a-h][1-8])/ )
	if( match_arr && match_arr[1] ) {
		this.file = match_arr[1];
		move = match_arr[2]+match_arr[3];
	}
	else if( this.src_sq ) {
		this.file = this.src_sq[0];
	}

	// rank
	var match_arr = move.match( /([1-8])([a-h][1-8])/ )
	if( match_arr && match_arr[1] ) {
		this.rank = parseInt(match_arr[1]);
		move = match_arr[2];
	}
	else if( this.src_sq ) {
		this.rank = this.src_sq[2];
	}

	// src_sq == file + rank
	if( this.src_sq && this.file && this.rank && this.src_sq != this.file+this.rank ) this.err( 'src_sq mismatch to file+rank' );

	// create src_sq if file + rank exists
	if( this.file && this.rank )	this.src_sq = this.file + this.rank;

	// have pared down move to be only target square
	this.tgt_sq = move;

	// might be capture w/o x notation or ep
	if( this.piece=='p' ) {
		if( !this.capture && this.get(this.tgt_sq)!=' ' ) this.capture = true;
		// todo: re-enable following
		this.ep_sq = this.getEnpassant();
		if( this.ep_sq == this.tgt_sq ) this.capture = true;
	}
	else	this.ep_sq = null;

	// apply move
	this.reApply();
}

// seq for troubleshooting
chess_spider.Move.nextSeq = 0;

// actually apply the move
chess_spider.Move.prototype.reApply = function() {

	// if plug has pulled on this move, forget about it
	//~if( !chess_spider.state.moves[this.id] ) return;
	
	// pull fresh pstn over
	this.pstn = this.pnt.pstn;

	// possible piece/src_sq for move
	var piece_sq_arr = (this.src_sq) ? new Array( this.src_sq ) : this.listPieceSquares( this.piece, this.color );

	// move
	if( !this.capture && !this.castle ) {

		var img_arr = new Array();

		// look for different source pieces
		for( var i=0; i<piece_sq_arr.length; i++ )
		if( this.listMoves( piece_sq_arr[i] ).has( this.tgt_sq ) ) {
			var sq = piece_sq_arr[i];
			if( this.file && this.file != chess_spider.util.getFile( sq ) ) continue;
			if( this.rank && this.rank != chess_spider.util.getRank( sq ) ) continue;
			img_arr.push( sq );
		}

		// error reporting
		if( img_arr.length == 0 )  this.err( 'cannot find move' );
		if( img_arr.length > 1 )  this.err( 'cannot determine piece' );

		// we know src_sq
		this.src_sq = img_arr[0];
	}

	// capture
	else if( this.capture ) {

		// look for different source pieces
		var img_arr = new Array();

		for( var i=0; i<piece_sq_arr.length; i++ ) {
			if( this.listCaptures( piece_sq_arr[i] ).has( this.tgt_sq ) ) {
				var sq = piece_sq_arr[i];
				if( this.file && this.file != chess_spider.util.getFile( sq ) ) continue;
				if( this.rank && this.rank != chess_spider.util.getRank( sq ) ) continue;
				img_arr.push( sq );
			}
		}

		// error reporting
		if( img_arr.length == 0 ) this.err( 'cannot find capture' );
		if( img_arr.length > 1 )  this.err( 'cannot determine capturing piece' );

		// we know src_sq
		this.src_sq = img_arr[0];
	}

	// user pawn on last rank
	if( this.piece=='p' && chess_spider.util.getRank(this.tgt_sq)==((this.color=='w')?8:1) && !this.promotion ) {

		// if not in user mode, error
		if( this.mode != 'user' ) this.err( 'no promotion given for move' );

		// default to queen promotion if not set
		this.promotion = 'q';
	}

	// clean move - part one
	// file - if needed
	this.file = ( this.piece == 'p' && this.capture ) ? chess_spider.util.getFile(this.src_sq) : null;
	this.rank = null;
	if( this.piece != 'p' && this.piece != 'k' ) {
		var pieces = this.listPieceSquares(this.piece,this.color);
		if( pieces.length > 1 ) {
			var file_ct = 0;
			var rank_ct = 0;
			for( var i=0; i<pieces.length; i++ ) 
			if( this.listMoves(pieces[i]).has(this.tgt_sq) ) {
				if( chess_spider.util.getFile(pieces[i]) == chess_spider.util.getFile(this.src_sq) ) file_ct++;
				if( chess_spider.util.getRank(pieces[i]) == chess_spider.util.getRank(this.src_sq) ) rank_ct++;
			}
			if( rank_ct > 1 ) this.file = chess_spider.util.getFile( this.src_sq );
			if( file_ct > 1 ) this.rank = chess_spider.util.getRank( this.src_sq );
		}
	}
	this.move  = (this.piece == 'p' ) ? '' : this.piece.toUpperCase();
	this.move += (this.file) ? this.file : '';
	this.move += (this.rank) ? this.rank : '';
	this.move += (this.capture) ? 'x' : '';
	this.move += this.tgt_sq;
	this.move += (this.promotion) ? '='+this.promotion.toUpperCase() : '';

	// do actual move - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	// clear from square
	this.set( this.src_sq, ' ' );

	// write to src_sq
	if( this.color == 'w' ) this.set( this.tgt_sq, (this.promotion) ? this.promotion.toUpperCase() : this.piece.toUpperCase() );
	else			this.set( this.tgt_sq, (this.promotion) ? this.promotion : this.piece );

	// castle 
	if( this.piece=='k' && this.src_sq=='e'+this.backrank() && this.tgt_sq=='g'+this.backrank() ) {
		this.move = this.castle = 'O-O';
		this.set( 'f'+((this.color=='w')?1:8), ((this.color=='w')?'R':'r') );
		this.set( 'h'+((this.color=='w')?1:8), ' ' );
	}
	else if( this.piece=='k' && this.src_sq=='e'+this.backrank() && this.tgt_sq=='c'+this.backrank() ) {
		this.move = this.castle = 'O-O-O';
		this.set( 'd'+((this.color=='w')?1:8), ((this.color=='w')?'R':'r') );
		this.set( 'a'+((this.color=='w')?1:8), ' ' );
	}
	
	// enpassant
	if( this.ep_sq && this.tgt_sq==this.ep_sq ) this.set( chess_spider.util.getFile(this.ep_sq)+((this.color=='w')?5:4), ' ' );

	// make sure king is not in check
	if( this.inCheck( this.color ) ) {
		this.err( 'cannot move king to check position' );
	}

	// if opp king checked / mated
	this.check = ( this.inCheck(this.oppColor()) );
	//-this.mate = ( this.check && this.inMate(this.oppColor()) );
	if( this.mate && !this.finish ) this.finish = (this.color=='w') ? 1 : 2;
	
	// clean move - part two
	this.move += (this.check) ? '+' : '';
	this.move += (this.mate) ? '#' : '';
	this.move += (this.nar) ? chess_spider.regx.nar_arr[this.nar] : '';
	this.move += (this.finish) ? ' '+chess_spider.regx.finish_disp_arr[this.finish] : '';
}

// previous for this move
chess_spider.Move.prototype.previous = function() {
	// look upstream until found
	for( var i=0, move = this.prv; move; move=move.prv, i++ ) {
		if( i > 100 ) throw( 'infinite loop in chess_spider.Move.prototype.previous' );
		//-console.log( 'previous: move_no='+move.move_no );
		if(( move.level == this.level || move.level == this.level - 1 )
		&& ( move.move_no == this.move_no   && this.color == 'b' && move.color == 'w'
		||   move.move_no == this.move_no-1 && this.color == 'w' && move.color == 'b' ) ) {
			return move;
		}
	}
	this.err( 'cannot find previous move' );
}

// branch_id for this move (deduce from previous)
chess_spider.Move.prototype.branch_id = function() {
	
	//-console.log( 'branch_id( move_no='+this.move_no + this.color + ' )' );

	// root
	if( this.level == 0 ) return this.move_no;

	var prv = this.previous();
	// level 1
	if( prv.level == 0 ) return prv.move_no + prv.color + this.branch;
	// one up
	if( prv.level == this.level-1 ) return prv.id.match(/B(.*)M/)[1] + ':' + prv.move_no + prv.color + this.branch;
	// sibling
	if( prv.level == this.level )   return prv.branch_id();
	// oops
	this.err( 'problem with branch_id' );
}

// build id
chess_spider.Move.prototype.genId = function() {
	//-console.log( 'branch_id = '+this.branch_id() );
	//-if( this.level==4 ) this.err( 'stop here for a minute' );
	return 'G' + this.game.idx + 'B' + this.branch_id() + 'M' + this.move_no + this.color;
}

// next move - will be on same level
chess_spider.Move.prototype.next = function() {
	if( this.level==0 ) return this.nxt;
	for( var mv = this.nxt; true; mv = mv.nxt ) {
		if( mv == null ) return null;
		if( mv.level == this.level && mv.branch == this.branch ) return mv;
		if( mv.level < this.level ) return null;
		if( mv.level == this.level && mv.branch > this.branch ) return null;
	}
}

// error handling
chess_spider.Move.prototype.err = function( msg ) {

	// build full error message
	var full_msg = msg+': '
		+ ' id='	+ this.id
		+ ' prv='	+((this.prv)?this.prv.id:'null')
		+ ' no='	+ this.move_no + this.color
		+ ' level='	+ this.level
		+ ' piece='	+ this.piece
		+ ' move="'	+ this.move + '"'
		+ ' file='	+ this.file
		+ ' rank='	+ this.rank
		+ ' src='	+ this.src_sq
		+ ' tgt='	+ this.tgt_sq
		+ ' castle='	+ this.castle
		+ ' capture='	+ this.capture
		+ ' finish='	+ this.finish
		;
	
	// todo: pick up real errors vs not found errors
	
	// remove references
	this.delete();

	// non-user: fatal error
	throw( full_msg );
}

// attach this move to board
// for new user moves - this move has been created by drag start but is attached on drag end
chess_spider.Move.prototype.attach = function( board ) {

	// error checking - are we in sync with board?
	if( this.pnt != board.cur_mv )  this.err( "move "+this.move_no+" drop error - id: board.cur_id=" + board.cur_mv.id+ ", this.pnt.id=" + this.pnt.id );

	// add to dom
	this.domAdd();

	// do the move
	board.userJump( this );
}

var user = false;

// node::= [<pre_comment><ws>][<move_no><ws>]<href><ws>[<post_comment><ws>]
// post_comment::= <comment_txt> | [<comment_txt1>]<board>[<comment_txt2>]
chess_spider.Move.prototype.domAdd = function() {

	// if we're root, forget about it - we've been created on display init
	if( !this.prv ) return;

	// put pieces into this frag
	var span = E.f( E.span( '.cs__move', this.showNo(), this.href() ) );

	// if previous move black and diferent branch, split it out
	if( this.color == 'w' && this.level>1 && this.prv.branch_id() != this.branch_id() ) this.prv.domSplit();

	// append span behind prv
	this.prv.dom_div().insertBefore( span, this.prv.dom_last().nextNode  );

	// div, join/split move
	this.domAdjust();

	// add comments
	this.showComments();

	// done
}

// slit dom move
chess_spider.Move.prototype.domSplit = function() {

	//-console.log( 'domSplit '+this.id+' - - - - - - - - - - - - - - - - - - - -' );

	// right and left
	var left = (this.color=='w') ? this : this.previous();
	var right = (this.color=='w') ? this.next() : this;

	//-console.log( 'left='+((left)?left.id:null)+', right='+((right)?right.id:null) );

	// cannot or already split
	if( !left || !right || left.dom_move() != right.dom_move() ) return;

	// new dom with right
	//-console.log( 'domSplit '+this.id );
	var span = E.span( '.cs__move', right.showNo(), right.dom_href() );
	left.dom_move().parentNode.insertBefore( span, left.dom_move().nextSibling );
}

// merges divs, joins move span, deletes deadwood as necessary
chess_spider.Move.prototype.domAdjust = function() {

	//-console.log( 'domAdjust '+this.id+' - - - - - - - - - - - - - - - - - - - -' );

	// step one: break out into new div because descending
	if( this.prv && this.branch_id() != this.prv.branch_id() && this.dom_div() == this.prv.dom_div() ) {

		// note: move span will not have been joined
		// note: there will have been no comments yet
		
		// curDiv - holds only cur move
		var curDiv = E.div( '.cs__l'+this.level, this.dom_move() );
		
		// cdrDiv - holds everything after cur move
		var cdrDiv = E.div( '.cs__l'+this.prv.level );
		//-console.log( this.prv.id+' dom_last: '+this.prv.dom_last().className );
		while( this.prv.dom_last().nextSibling ) {
			//-console.log( 'move to cdr: '+this.prv.dom_last().nextSibling.tagName );
			cdrDiv.appendChild( this.prv.dom_last().nextSibling );
		}
		
		// put new divs - note reverse insert!! final order reverse of insert order
		//-console.log( 'creating cur div '+curDiv.className+' and cdr div '+cdrDiv.className+' ('+cdrDiv.children.length+')' );
		this.prv.dom_div().parentNode.insertBefore( cdrDiv, this.prv.dom_div().nextSibling );
		this.prv.dom_div().parentNode.insertBefore( curDiv, this.prv.dom_div().nextSibling );
	}

	// step two.1: remove deadwood first
	while( this.dom_div().nextSibling && this.dom_div().nextSibling.children.length == 0 ) {
		//-console.log( 'deleting right div: '+this.dom_div().nextSibling.className );
		this.dom_div().parentNode.removeChild( this.dom_div().nextSibling );
	}

	// step two.2: combine divs
	if( this.prv && this.prv.dom_div().nextSibling == this.dom_div() && this.prv.branch_id() == this.branch_id() ) {
		//-console.log( 'combining divs left: '+this.prv.dom_div().className +' with '+this.dom_div().className );
		this.prv.dom_div().merge( this.dom_div() );
		//-while( this.dom_div().firstChild ) this.prv.dom_div().appendChild( this.dom_div().firstChild );
	}
	if( this.nxt && this.nxt.dom_href() && this.dom_div().nextSibling == this.nxt.dom_div() && this.branch_id() == this.nxt.branch_id() ) {
		//-console.log( 'combining divs right: '+this.dom_div().className +' with '+((this.nxt.dom_div())?this.nxt.dom_div().className:'nxt') );
		this.dom_div().merge( this.nxt.dom_div() );
		//-while( this.nxt.dom_div().firstChild ) this.dom_div().appendChild( this.nxt.dom_div().firstChild );
	}

	// step three: join span move

	// right and left
	var left = (this.color=='w') ? this : this.prv;
	var right = (this.color=='w') ? this.nxt : this;

	// make sure adjacent physically and logically
	// note: split for insert branch is done at domAdd and showComment
	if( left && left.dom_href() && right && right.dom_href() && left.next() == right ) {

		//-console.log( 'domAdjust step 3: left='+left.id+', right='+right.id );

		// whether to join
		var join = (!left.comment && !left.ucomment && !right.pre_comment );

		// whether joined
		var joined = ( left.dom_move() == right.dom_move() );

		// something to do
		if( join != joined ) {

			// split
			if( !join && joined ) console.log( 'ERROR: we should not see the need for a split' );
			/*
				console.log( 'splitting '+right.id+' from '+left.id );
				var span = E.span( '.cs_move', right.showNo(), right.dom_href() );
				if( right.dom_pre_comment() ) right.dom_pre_comment().parentNode.insertBefore( span, right.dom_pre_comment().nextSibling );
				else if( left.ucomment() ) left.dom_pre_comment().parentNode.insertBefore( span, left.dom_ucomment().nextSibling );
				else if( left.comment() ) left.dom_pre_comment().parentNode.insertBefore( span, left.dom_comment().nextSibling );
				else left.dom_div().insertBefore( span, left.dom_move().nextSibling );
			*/	

			// join move span
			else if( join && !joined ) {
				//-console.log( 'joining '+left.id+' to '+right.id );
				//-console.log( 'left: '+left.dom_move().innerHTML );
				//-console.log( 'right: '+right.dom_move().innerHTML );
				if( right.dom_href() ) {
					var right_dom = right.dom_move();
					left.dom_move().appendChild( right.dom_href() );
					right_dom.parentNode.removeChild( right_dom );
				}
				//-console.log( 'new left: '+left.dom_move().innerHTML );
			}
		}
	}

	// step 4: remove deadwood
	while( this.dom_div()
	&& this.dom_div().nextSibling
	&& !this.dom_div().nextSibling.children.length ) {
		//-console.log( 'deleting right div: '+this.dom_div().nextSibling.className );
		this.dom_div().parentNode.removeChild( this.dom_div().nextSibling );
	}
}

chess_spider.Move.prototype.showComments = function() {

	// if root move or no href (yet), ignore
	if( !this.prv || !this.dom_href() ) return;

	// for closure functions
	var mv = this;

	//-console.log( 'this='+this.id+' pre="'+this.pre_comment+'", comment="'+this.comment+'", ucomment="'+this.ucomment );

	// whether we found board
	// issue is that we can have either zero on one board for a move; board can be in either comment or ucomment
	// if we find need for board, we wish to use existent board if exists
	var board_found = false;

	// pre_comment - - - - - - - - - - - - - - - - - - - -

	var dom = this.game.dom_movelist.querySelector( "span.cs__pre_comment[move_id='"+this.id+"']");

	// delete left comment if exists in dom but not in move
	if( !this.pre_comment && dom ) {
		dom.parentNode.removeChild( dom );
	}

	// replace pre_comment
	else if( this.pre_comment && dom ) dom.replaceE( this.pre_comment );

	// new pre_comment
	else if( this.pre_comment ) {
		if( this.color == 'b' ) this.domSplit();
		this.dom_div().insertBefore( E.span( '.cs__pre_comment', {'a.move_id':this.id,'text':this.pre_comment} ), this.dom_move() );
	}

	// comment - - - - - - - - - - - - - - - - - - - -

	var dom = this.game.dom_movelist.querySelector( "span.cs__comment[move_id='"+this.id+"']");

	// remove from dom
	if( !this.comment && dom ) dom.parentNode.removeChild( dom );

	// create comment body, insert
	else if( this.comment ) {

		var frag = comment2frag( this.comment );

		// remove comment if exists; otherwise split if necessary
		if( dom ) dom.parentNode.removeChild( dom );
		else if( this.color == 'w' ) this.domSplit();

		// new or replaced comment
		this.dom_div().insertBefore( E.span('.cs__comment', {'a.move_id':this.id}, frag), this.dom_move().nextSibling );
	}
			
	// ucomment - - - - - - - - - - - - - - - - - - - -

	var dom = this.game.dom_movelist.querySelector( "span.cs__ucomment[move_id='"+this.id+"']");

	// remove from dom
	if( !this.ucomment && dom ) dom.parentNode.removeChild( dom );

	// create comment body, insert
	else if( this.ucomment ) {

		var frag = comment2frag( this.comment );

		// remove ucomment if exists; otherwise split if necessary
		if( dom ) dom.parentNode.removeChild( dom );
		else if( this.color == 'w' ) this.domSplit();

		// new or replaced comment
		this.dom_div().insertBefore( E.span('.cs__ucomment', {'a.move_id':this.move_id}, frag), this.dom_last().nextSibling );
	}

	// get rid of board if no longer used
	if( this.board && !board_found ) {

		// if this board is cur bd for game, find another board
		if( this.game.cur_bd == this.board ) {

			// all boards available
			var boards = chess_spider.Board.boards();

			// look board from this game not this board
			for( var i=0; i<boards.length; i++ )
			if( boards[i].root_mv && boards[i].root_mv.game == this.game && boards[i]!=this.board ) {
				this.game.cur_bd = boards[i];
				break;
			}
			if( i>=boards.length ) this.game.cur_bd = null;
		}
		
		// rid this board
		this.board = null;
	}

	// if we have board and no cur_bd for game, set this as such
	if( this.board && !this.game.cur_bd ) this.game.cur_bd = this.board;

	// crete comment span for comment or ucomment
	// board_found determines whether cannot create new (another) board	
	function comment2frag( comment ) {

		// split comment into car/cdr around #
		// if already board, ignore # sign
		var offset = (board_found) ? -1 : comment.indexOf('#');
		var car = ( offset > -1 ) ? comment.substr(0,offset) : comment;
		var cdr = ( offset > -1 ) ? comment.substr(offset+1) : '';

		// add board
		if( offset > -1 ) {
			board_found = true;
			if( !mv.board ) mv.board = new chess_spider.Board( mv );
		}

		// done
		// build frag
		var frag = E.f( ((car.length)? car : null ),
				((offset>-1) ? mv.board.dom_div : null ),
				((cdr.length)? cdr : null ) );

		return frag;
	}
}

// show number for this move if appropriate
// creates the span but rather the string for move_no - null if no move null should be shown
chess_spider.Move.prototype.showNo = function() {

	// place into span
	return E.span( '.cs__move_no', {'text':this.move_no+((this.color=='w')?'.':'...')} );
}


// if recursive (all==true) delete last leaf first
chess_spider.Move.prototype.userDelete = function( all ) {

	// make sure user move
	if( this.mode != 'user' ) return;

	//-console.log( "userDelete "+this.id+": "+all );

	// if all, get rid of siblings in reverse order
	// todo: when wouldn't this be true?
	if( all ) {
		// delete children
		for( var i=0; i<this.branch_arr.length; i++ ) this.branch_arr[i].userDelete( true );
		// delete to right - this will be recursive, so right will always be deleted first
		if( this.next() ) this.next().userDelete( true );
	}

	// make sure we're the last in the node
	if( this.next() ) return false;

	// remove reference to this last
	this.delete();

	// we did the delete
	return true;
}

// symbolic move
chess_spider.Move.prototype.getMove = function() {
	var move = this.move;
	if( chess_spider.params.control.disp_font=='symbol' ) move = move.replace(/x/,"\u00d7");
	if( this.piece != 'p' && !this.castle ) move = chess_spider.params.fonts[chess_spider.params.control.disp_font][this.color + this.piece] + move.substr(1);
	return move;
}

// net move from this move - return false if invalid
chess_spider.Move.prototype.href = function() {

	var href = E.a( 'move', chess_spider.events.moveJump, 
			{'title':" id="+this.id+" prv="+this.prv.id+" pnt="+this.pnt.id+" lvl="+this.level+" nxt="+((this.nxt)?this.nxt.id:'null')+" src="+this.src_sq,
			'a.move_id':this.id, 'text':
		this.getMove()
		+ ((this.nar!=null) ? chess_spider.regx.nar_arr[this.nar] : '' )
		+ ((this.finish) ? ' '+chess_spider.regx.finish_disp_arr[this.finish] : '' )});
	return href;
}

/* deprecated - no advantage for jump to be a closure call
// trying this
chess_spider.Move.jump = function( e ) {

	alert( this.move );

	// stop ripple
	e.stopPropagation();
	e.preventDefault();
}
*/

// net move from this move - throw if invalid
// theorem: this will always be genMove's parent
chess_spider.Move.prototype.genMove = function( board, src_sq, tgt_sq ) {

	// same square, forget it
	if( src_sq == tgt_sq ) throw( 'same square' );

	// get real previous move - - - - - - - - - - - - - - - - - - - -
	
	// root_mv
	if( this.level == 0 ) {
		var level = 1;
		var prv = this;
	}

	// regular move
	else {
		// when need to push this a level down?  when nxt == next()
		var level = ( this.level==0 || this.nxt && this.nxt == this.next() ) ? this.level+1 : this.level;

		// going down
		// theorem: can never be going up since this is parent
		if( level > this.level ) var prv = this.next();

		// if previous move has branch, then previous is last move of branch
		else if( this.prv.level == this.level && this.prv.branch_arr.length > 0 ) var prv = this.prv.branch_arr[this.prv.branch_arr.length-1].lastMove();

		// prv == pnt
		else	var prv = this;
	}

	// build move notation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	// whether capture
	var capture = ( this.get(tgt_sq) != ' ' );

	// piece
	var piece = this.get(src_sq).toLowerCase();

	// color
	var color = this.getColor(src_sq);

	// move_no
	var move_no = (color=='w') ? parseInt(this.move_no) + 1 : this.move_no;

	// pass crappy but accurate move - let move clean it up later
	if( piece == 'p' && capture )				move = src_sq + 'x' + tgt_sq;
	else if( capture )					move = piece.toUpperCase() + src_sq + 'x' + tgt_sq;
	else if( !capture )					move = piece.toUpperCase() + src_sq + '-' + tgt_sq;

	//-console.log( 'chess_spider.Move( prv='+prv.id+', move_no='+move_no+', color='+color+', move='+move+', level='+level+', user )' );
	var move = new chess_spider.Move( this.game, prv, move_no, color, move, level, null, null, 'user' );

	return move;
}

// delete move - remove from all contexts
chess_spider.Move.prototype.delete = function() {

	//-console.log( 'delete '+this.id+' to '+this.tgt_sq+', seq='+this.seq );
	
	// remember previous
	var prv = this.prv;

	// double linked list
	if( this.nxt ) this.nxt.prv = this.prv;
	if( this.prv ) this.prv.nxt = this.nxt;

	// if child of parent
	var pntIndex = this.pnt.branch_arr.indexOf( this );
	if( pntIndex >= 0 ) this.pnt.branch_arr.remove(pntIndex);

	// if cur_idx on any board, move to parent
	var board_arr = chess_spider.Board.boards();
	for( i=0; i<board_arr.length; i++ )
	if( board_arr[i].cur_mv == this ) {
		board_arr[i].userJump( this.pnt );
	}

	// rid everything in movelist
	if( this.dom_href() ) {

		// delete move span iff only move_no and single move_no + href inside
		if( this.dom_move().children.length == 2 ) this.dom_move().parentNode.removeChild( this.dom_move() );
		else this.dom_move().removeChild( this.dom_href() );

		// remove all others in movelist
		if( this.pre_comment )	this.dom_pre_comment().parentNode.removeChild( this.dom_pre_comment() );
		if( this.comment )	this.dom_comment().parentNode.removeChild( this.dom_comment() );
		if( this.ucomment )	this.dom_ucomment().parentNode.removeChild( this.dom_ucomment() );

		// have prv clean up the mess
		prv.domAdjust();
	}

	// remove from moves
	delete chess_spider.state.moves[this.id];
}

// get from current pstn
chess_spider.Move.prototype.getColor = function( square ) {
	var piece = this.get( square );
	if( piece == ' ' ) return null;
	if( piece >= 'a' ) return 'b';
	return 'w';
}

// get from current pstn
chess_spider.Move.prototype.get = function( square ) {
	var file = chess_spider.util.ordFile(square[0]);
	var rank = parseInt(square[1]);
	var offset = ((rank-1)*8) + (file-1);

	// return what we know
	return this.pstn[offset];
}

// set from current pstn
chess_spider.Move.prototype.set = function( square, piece ) {
	if( square == undefined ) this.err( 'set(undefined,'+piece+') - pstn="'+this.pstn+'"' );
	var file = chess_spider.util.ordFile(square[0]);
	var rank = parseInt(square[1]);
	var offset = ((rank-1)*8) + (file-1);

	// set
	this.pstn = ((offset)?this.pstn.substr(0,offset):'') + piece + this.pstn.substr(offset+1);
	//-alert( "set( "+square+", "+piece+" ):  file="+file+", rank="+rank+", offset=" + offset + ", pstn=[" + this.pstn.replace(/ /,'+') + ']' );
}


// list squares holding piece
// to get all squares of a color, have piece = null
chess_spider.Move.prototype.listPieceSquares = function( piece, color ) {
	if( piece && color ) piece = (color=='w') ? piece.toUpperCase() : piece.toLowerCase();
	var list = new Array();
	for( var rank = 1; rank <= 8; rank = chess_spider.util.incRank( rank ) )
		for( var file = 'a'; file <= 'h'; file = chess_spider.util.incFile( file ) ) {
			if( piece == null && this.getColor(file+rank) == color )	list.push( file+rank );
			else if( this.get(file+rank) == piece )				list.push( file+rank );
		}
	return list;
}

chess_spider.Move.prototype.listMoves = function( sq, piece, color ) {

	//-console.log( "listMoves( "+sq+','+piece+','+color+' )' );
	if( piece == null ) piece = this.get(sq);
	if( color == null ) color = (piece>='a') ? 'b' : 'w';
	piece = piece.toLowerCase();
	var rank = chess_spider.util.getRank(sq);
	var file = chess_spider.util.getFile(sq);
	var list = new Array();

	// pawn
	if( piece == 'p' ) {
		if( color == 'w' ) {
			if( this.get(file+chess_spider.util.incRank(rank))==' ' ) list.push( file+chess_spider.util.incRank(rank) );
			if( rank == 2 && this.get(file+3)==' ' && this.get(file+4)==' ' ) list.push( file+4 );
		}
		else if( color == 'b' ) {
			if( this.get(file+chess_spider.util.decRank(rank))==' ' ) list.push( file+chess_spider.util.decRank(rank) );
			if( rank == 7 && this.get(file+6)==' ' && this.get(file+5)==' ' ) list.push( file+5 );
		}
	}

	// rook/queen - horizonal and vertical
	if( piece == 'r' || piece == 'q' ) {
		for( var r=chess_spider.util.incRank(rank); r<=8; r=chess_spider.util.incRank(r) ) {
			if( this.get(file+r) != ' ' ) break;
			list.push( file+r );
		}
		for( var r=chess_spider.util.decRank(rank); r>=1; r=chess_spider.util.decRank(r) ) {
			if( this.get(file+r) != ' ' ) break;
			list.push( file+r );
		}
		for( var f=chess_spider.util.incFile(file); f<='h'; f=chess_spider.util.incFile(f) ) {
			if( this.get(f+rank) != ' ' ) break;
			list.push( f+rank );
		}
		for( var f=chess_spider.util.decFile(file); f>='a'; f=chess_spider.util.decFile(f) ) {
			if( this.get(f+rank) != ' ' ) break;
			list.push( f+rank );
		}
	}

	// biship/queen - diagonal
	if( piece == 'b' || piece == 'q' ) {
		for( var r=chess_spider.util.incRank(rank), f=chess_spider.util.incFile(file); r<=8 && f<='h'; r=chess_spider.util.incRank(r), f=chess_spider.util.incFile(f) ) {
			if( this.get(f+r) != ' ' ) break;
			list.push( f+r );
		}
		for( var r=chess_spider.util.incRank(rank), f=chess_spider.util.decFile(file); r<=8 && f>='a'; r=chess_spider.util.incRank(r), f=chess_spider.util.decFile(f) ) {
			if( this.get(f+r) != ' ' ) break;
			list.push( f+r );
		}
		for( var r=chess_spider.util.decRank(rank), f=chess_spider.util.decFile(file); r>=1 && f>='a'; r=chess_spider.util.decRank(r), f=chess_spider.util.decFile(f) ) {
			if( this.get(f+r) != ' ' ) break;
			list.push( f+r );
		}
		for( var r=chess_spider.util.decRank(rank), f=chess_spider.util.incFile(file); r>=1 && f<='h'; r=chess_spider.util.decRank(r), f=chess_spider.util.incFile(f) ) {
			if( this.get(f+r) != ' ' ) break;
			list.push( f+r );
		}
	}

	// knight
	if( piece == 'n' ) {
		if( this.get(chess_spider.util.incFile(file)+chess_spider.util.inc2Rank(rank))==' ' ) list.push( chess_spider.util.incFile(file)+chess_spider.util.inc2Rank(rank) );
		if( this.get(chess_spider.util.incFile(file)+chess_spider.util.dec2Rank(rank))==' ' ) list.push( chess_spider.util.incFile(file)+chess_spider.util.dec2Rank(rank) );
		if( this.get(chess_spider.util.decFile(file)+chess_spider.util.inc2Rank(rank))==' ' ) list.push( chess_spider.util.decFile(file)+chess_spider.util.inc2Rank(rank) );
		if( this.get(chess_spider.util.decFile(file)+chess_spider.util.dec2Rank(rank))==' ' ) list.push( chess_spider.util.decFile(file)+chess_spider.util.dec2Rank(rank) );
		if( this.get(chess_spider.util.inc2File(file)+chess_spider.util.incRank(rank))==' ' ) list.push( chess_spider.util.inc2File(file)+chess_spider.util.incRank(rank) );
		if( this.get(chess_spider.util.inc2File(file)+chess_spider.util.decRank(rank))==' ' ) list.push( chess_spider.util.inc2File(file)+chess_spider.util.decRank(rank) );
		if( this.get(chess_spider.util.dec2File(file)+chess_spider.util.incRank(rank))==' ' ) list.push( chess_spider.util.dec2File(file)+chess_spider.util.incRank(rank) );
		if( this.get(chess_spider.util.dec2File(file)+chess_spider.util.decRank(rank))==' ' ) list.push( chess_spider.util.dec2File(file)+chess_spider.util.decRank(rank) );
	}

	// king
	if( piece == 'k' ) {
		if( this.get(file+chess_spider.util.incRank(rank))==' ' ) list.push( file+chess_spider.util.incRank(rank) );
		if( this.get(file+chess_spider.util.decRank(rank))==' ' ) list.push( file+chess_spider.util.decRank(rank) );
		if( this.get(chess_spider.util.incFile(file)+rank)==' ' ) list.push( chess_spider.util.incFile(file)+rank );
		if( this.get(chess_spider.util.decFile(file)+rank)==' ' ) list.push( chess_spider.util.decFile(file)+rank );
		if( this.get(chess_spider.util.incFile(file)+chess_spider.util.incRank(rank))==' ' ) list.push( chess_spider.util.incFile(file)+chess_spider.util.incRank(rank) );
		if( this.get(chess_spider.util.incFile(file)+chess_spider.util.decRank(rank))==' ' ) list.push( chess_spider.util.incFile(file)+chess_spider.util.decRank(rank) );
		if( this.get(chess_spider.util.decFile(file)+chess_spider.util.incRank(rank))==' ' ) list.push( chess_spider.util.decFile(file)+chess_spider.util.incRank(rank) );
		if( this.get(chess_spider.util.decFile(file)+chess_spider.util.decRank(rank))==' ' ) list.push( chess_spider.util.decFile(file)+chess_spider.util.decRank(rank) );

		// if in user mode, castling
		var castles = this.listCastles( color );
		if( castles.has('O-O') )	list.push( 'g'+((color=='w')?1:8) );
		if( castles.has('O-O-O') )	list.push( 'c'+((color=='w')?1:8) );
	}

	return list;
}

chess_spider.Move.prototype.listCaptures = function( sq, piece, color ) {

	if( piece == null ) piece = this.get(sq);
	if( color == null ) color = (piece>='a') ? 'b' : 'w';
	var oppColor = (color=='w') ? 'b' : 'w';
	piece = piece.toLowerCase();
	var rank = chess_spider.util.getRank(sq);
	var file = chess_spider.util.getFile(sq);
	var list = new Array();

	// pawn
	if( piece == 'p' ) {
		if( color == 'w' ) {
			if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.incRank(rank))=='b' || chess_spider.util.incFile(file)+chess_spider.util.incRank(rank)==this.ep_sq ) list.push( chess_spider.util.incFile(file)+chess_spider.util.incRank(rank) );
			if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.incRank(rank))=='b' || chess_spider.util.decFile(file)+chess_spider.util.incRank(rank)==this.ep_sq ) list.push( chess_spider.util.decFile(file)+chess_spider.util.incRank(rank) );
		}
		else if( color == 'b' ) {
			if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.decRank(rank))=='w' || chess_spider.util.incFile(file)+chess_spider.util.decRank(rank)==this.ep_sq ) list.push( chess_spider.util.incFile(file)+chess_spider.util.decRank(rank) );
			if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.decRank(rank))=='w' || chess_spider.util.decFile(file)+chess_spider.util.decRank(rank)==this.ep_sq ) list.push( chess_spider.util.decFile(file)+chess_spider.util.decRank(rank) );
		}
	}

	// rook/queen - horizonal and vertical
	if( piece == 'r' || piece == 'q' ) {
		for( var r=chess_spider.util.incRank(rank); r<=8; r=chess_spider.util.incRank(r) ) {
			if( this.getColor(file+r) == oppColor ) list.push( file+r );
			if( this.get(file+r) != ' ' ) break;
		}
		for( var r=chess_spider.util.decRank(rank); r>=1; r=chess_spider.util.decRank(r) ) {
			if( this.getColor(file+r) == oppColor ) list.push( file+r );
			if( this.get(file+r) != ' ' ) break;
		}
		for( var f=chess_spider.util.incFile(file); f<='h'; f=chess_spider.util.incFile(f) ) {
			if( this.getColor(f+rank) == oppColor ) list.push( f+rank );
			if( this.get(f+rank) != ' ' ) break;
		}
		for( var f=chess_spider.util.decFile(file); f>='a'; f=chess_spider.util.decFile(f) ) {
			if( this.getColor(f+rank) == oppColor ) list.push( f+rank );
			if( this.get(f+rank) != ' ' ) break;
		}
	}

	// biship/queen - diagonal
	if( piece == 'b' || piece == 'q' ) {
		for( var r=chess_spider.util.incRank(rank), f=chess_spider.util.incFile(file); r<=8 && f<='h'; r=chess_spider.util.incRank(r), f=chess_spider.util.incFile(f) ) {
			if( this.getColor(f+r) == oppColor ) list.push( f+r );
			if( this.get(f+r) != ' ' ) break;
		}
		for( var r=chess_spider.util.incRank(rank), f=chess_spider.util.decFile(file); r<=8 && f>='a'; r=chess_spider.util.incRank(r), f=chess_spider.util.decFile(f) ) {
			if( this.getColor(f+r) == oppColor ) list.push( f+r );
			if( this.get(f+r) != ' ' ) break;
		}
		for( var r=chess_spider.util.decRank(rank), f=chess_spider.util.decFile(file); r>=1 && f>='a'; r=chess_spider.util.decRank(r), f=chess_spider.util.decFile(f) ) {
			if( this.getColor(f+r) == oppColor ) list.push( f+r );
			if( this.get(f+r) != ' ' ) break;
		}
		for( var r=chess_spider.util.decRank(rank), f=chess_spider.util.incFile(file); r>=1 && f<='h'; r=chess_spider.util.decRank(r), f=chess_spider.util.incFile(f) ) {
			if( this.getColor(f+r) == oppColor ) list.push( f+r );
			if( this.get(f+r) != ' ' ) break;
		}
	}

	// knight
	if( piece == 'n' ) {
		if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.inc2Rank(rank))==oppColor ) list.push( chess_spider.util.incFile(file)+chess_spider.util.inc2Rank(rank) );
		if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.dec2Rank(rank))==oppColor ) list.push( chess_spider.util.incFile(file)+chess_spider.util.dec2Rank(rank) );
		if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.inc2Rank(rank))==oppColor ) list.push( chess_spider.util.decFile(file)+chess_spider.util.inc2Rank(rank) );
		if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.dec2Rank(rank))==oppColor ) list.push( chess_spider.util.decFile(file)+chess_spider.util.dec2Rank(rank) );
		if( this.getColor(chess_spider.util.inc2File(file)+chess_spider.util.incRank(rank))==oppColor ) list.push( chess_spider.util.inc2File(file)+chess_spider.util.incRank(rank) );
		if( this.getColor(chess_spider.util.inc2File(file)+chess_spider.util.decRank(rank))==oppColor ) list.push( chess_spider.util.inc2File(file)+chess_spider.util.decRank(rank) );
		if( this.getColor(chess_spider.util.dec2File(file)+chess_spider.util.incRank(rank))==oppColor ) list.push( chess_spider.util.dec2File(file)+chess_spider.util.incRank(rank) );
		if( this.getColor(chess_spider.util.dec2File(file)+chess_spider.util.decRank(rank))==oppColor ) list.push( chess_spider.util.dec2File(file)+chess_spider.util.decRank(rank) );
	}

	// king
	if( piece == 'k' ) {
		if( this.getColor(file+chess_spider.util.incRank(rank))==oppColor ) list.push( file+chess_spider.util.incRank(rank) );
		if( this.getColor(file+chess_spider.util.decRank(rank))==oppColor ) list.push( file+chess_spider.util.decRank(rank) );
		if( this.getColor(chess_spider.util.incFile(file)+rank)==oppColor ) list.push( chess_spider.util.incFile(file)+rank );
		if( this.getColor(chess_spider.util.decFile(file)+rank)==oppColor ) list.push( chess_spider.util.decFile(file)+rank );
		if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.incRank(rank))==oppColor ) list.push( chess_spider.util.incFile(file)+chess_spider.util.incRank(rank) );
		if( this.getColor(chess_spider.util.incFile(file)+chess_spider.util.decRank(rank))==oppColor ) list.push( chess_spider.util.incFile(file)+chess_spider.util.decRank(rank) );
		if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.incRank(rank))==oppColor ) list.push( chess_spider.util.decFile(file)+chess_spider.util.incRank(rank) );
		if( this.getColor(chess_spider.util.decFile(file)+chess_spider.util.decRank(rank))==oppColor ) list.push( chess_spider.util.decFile(file)+chess_spider.util.decRank(rank) );
	}

	return list;
}


chess_spider.Move.prototype.getEnpassant = function() {

	// en passant
	var pntMove = this.pnt;
	if( pntMove.piece == 'p'
			&&  chess_spider.util.getFile(pntMove.tgt_sq) == chess_spider.util.getFile(pntMove.tgt_sq)
			&&  Math.abs(chess_spider.util.getRank(pntMove.src_sq)-chess_spider.util.getRank(pntMove.tgt_sq))==2 
			&&  ( (pntMove.color == 'b' && chess_spider.util.getRank(this.tgt_sq)==6) || (pntMove.color=='w' && chess_spider.util.getRank(this.tgt_sq)==3) ) ) {
		return chess_spider.util.getFile(pntMove.tgt_sq) + ((pntMove.color=='w')?'3':'6');
	}

	else	return null;
}

// castle
chess_spider.Move.prototype.listCastles = function( color ) {

	var list = new Array();
	var kingside = true;
	var queenside = true;
	var colorKing = (color=='w') ? 'K' : 'k';
	var colorRook = (color=='w') ? 'R' : 'r';

	// king and rook in place, empty spaces in between
	if( this.get('e'+this.backrank())!=colorKing || this.get('h'+this.backrank())!=colorRook ) kingside = false;
	if( this.get('e'+this.backrank())!=colorKing || this.get('a'+this.backrank())!=colorRook ) queenside = false;
	if( this.get('f'+this.backrank())!=' ' || this.get('g'+this.backrank())!=' ' ) kingside = false;
	if( this.get('d'+this.backrank())!=' ' || this.get('c'+this.backrank())!=' ' || this.get('b'+this.backrank())!=' ' ) queenside = false;

	// king or rook has moved
	for( var prv=this.pnt; (kingside || queenside) && prv.pnt.prv; prv=prv.pnt ) {
		if( prv.src_sq == 'e'+this.backrank() ) kingside = false;
		if( prv.src_sq == 'h'+this.backrank() ) kingside = false;
		if( prv.src_sq == 'e'+this.backrank() ) queenside = false;
		if( prv.src_sq == 'a'+this.backrank() ) queenside = false;
	}

	// todo: make sure king does not enter check
	if( (kingside || queenside) && this.inCheck(color) ) kingside = queenside = false;
	if( kingside ) {
		this.pstnPush( 'e'+this.backrank(),' ', 'f'+this.backrank(),colorKing );
		if( this.inCheck(color) ) kingside = false;
		this.pstnPop();
	}
	if( kingside ) {
		this.pstnPush( 'e'+this.backrank(),' ', 'g'+this.backrank(),colorKing );
		if( this.inCheck(color) ) kingside = false;
		this.pstnPop();
	}
	if( queenside ) {
		this.pstnPush( 'e'+this.backrank(),' ', 'd'+this.backrank(),colorKing );
		if( this.inCheck(color) ) kingside = false;
		this.pstnPop();
	}
	if( queenside ) {
		this.pstnPush( 'e'+this.backrank(),' ', 'f'+this.backrank(),colorKing );
		if( this.inCheck(color) ) kingside = false;
		this.pstnPop();
	}

	// build list
	if( kingside ) list.push( 'O-O' );
	if( queenside ) list.push( 'O-O-O' );

	return list;
}

// whether king is in check 
chess_spider.Move.prototype.inCheck = function( color ) {

	// what color to check for
	if( !color ) color = this.color;
	
	// collect all possible moves
	var sq_arr = this.listPieceSquares( null, (color=='w')?'b':'w' );
	var king_sq = this.listPieceSquares( 'k', color )[0];
	for( var j=0; j<sq_arr.length; j++ ) {
		if( this.listCaptures( sq_arr[j] ).has( king_sq ) ) return true;
	}
	return false;
}

// whether king is mated 
// color will always be opposite color
chess_spider.Move.prototype.inMate = function( color ) {

	// move_no > 2
	if( this.move_no < 2 ) return false;

	// what color to check for
	if( !color ) color = this.color;

	// color must be opp color
	if( color == this.color ) this.err( 'inMate() can only test for opp mate' );

	// need to be in check now
	if( !this.inCheck(color) ) return false;
	
	// collect all possible piece squares for opp to move
	var sq_arr = this.listPieceSquares( null, color );

	// make sure check for each possible move for each possible piece
	for( var i=0; i<sq_arr.length; i++ ) {

		// possible moves
		var mv_arr = this.listMoves( sq_arr[i] );
		for( var j=0; j<mv_arr.length; j++ ) {
			var nextMove = new chess_spider.Move( this.game, this.idx, (this.color=='w')?this.move_no:this.move_no+1, this.oppColor(),
						sq_arr[i]+mv_arr[j], this.level, null, null, 'trial' );
			//-if( !nextMove.inCheck() ) return false;
		}

		// possible captures
		var mv_arr = this.listCaptures( sq_arr[i] );
		for( var j=0; j<mv_arr.length; j++ ) {
			//-var nextMove = new Move( this.game, this.idx, (this.color=='w')?this.move_no:this.move_no+1, this.oppColor(),
						//-sq_arr[i]+'x'+mv_arr[j], this.level, null, null, 'trial' );
			//-if( !nextMove.inCheck() ) return false;
		}
	}
	
	return true;
}

// 

// push/pop pstn
chess_spider.Move.prototype.pstnPush = function( s1, p1, s2, p2 ) {
	if( this.stackPstn == undefined ) this.stackPstn = new Array();
	this.stackPstn.push( this.pstn );
	if( s1 && p1 ) this.set( s1, p1 );
	if( s2 && p2 ) this.set( s2, p2 );
}
chess_spider.Move.prototype.pstnPop = function() {
	this.pstn = this.stackPstn.pop();
}

chess_spider.Move.prototype.oppColor = function( color ) {
	if( !color ) color = this.color;
	return ( color == 'w' ) ? 'b' : 'w';
}

// wether other move is nextmove for this move
// ignores levels!
chess_spider.Move.prototype.isNextMove = function( nxt ) {
	
	// special case for root
	if( nxt.move_no == 1 && nxt.color == 'w' && this.prv == null ) return true;

	// normal case
	var result = ( (this.move_no == nxt.move_no && this.color == 'w' && nxt.color == 'b' )
		 || (this.move_no+1 == nxt.move_no && this.color == 'b' && nxt.color == 'w' ) );

	return result;
}

chess_spider.Move.prototype.isLowerMove = function( nxt ) {

	// special case for root
	if( this.prv == null && this != nxt ) return true;

	// normal case
	return( (this.move_no == nxt.move_no && this.color == 'w' && nxt.color == 'b' )
		 || (this.move_no < nxt.move_no ) );
}


// last dom for this move
chess_spider.Move.prototype.getLastDomNode = function() {

	if( this.dom_post_ws ) return this.dom_post_ws;
	if( this.dom_post ) return this.dom_post;
	if( this.dom_href_ws ) return this.dom_href_ws;
	if( this.dom_href() ) return this.dom_href();
	this.err( 'cannot get getLastNode for '+this.id );
}

// for reporting branch nodes
chess_spider.Move.prototype.branches = function() {
	var arr = new Array();
	for( var i=0; i<this.branch_arr.length; i++ ) arr.push( this.branch_arr[i].id );
	return arr.join(',');
}


// helper functions

chess_spider.Move.prototype.dom_href = function() {
	if( !this.game.dom_movelist ) return null;
	else return this.game.dom_movelist.querySelector("a[move_id='"+this.id+"']"); }
chess_spider.Move.prototype.dom_move = function() {
	if( !this.dom_href() ) return null;
	else return this.dom_href().parentNode; }
chess_spider.Move.prototype.dom_div = function() {
	if( !this.dom_move() ) return null;
	else return this.dom_move().parentNode; }
chess_spider.Move.prototype.dom_pre_comment = function() {
	if( !this.dom_href() ) return null;
	else return this.game.dom_movelist.querySelector("span.cs__pre_comment[move_id='"+this.id+"']"); }
chess_spider.Move.prototype.dom_comment = function() {
	if( !this.dom_href() ) return null;
	else return this.game.dom_movelist.querySelector("span.cs__comment[move_id='"+this.id+"']"); }
chess_spider.Move.prototype.dom_ucomment = function() {
	if( !this.dom_href() ) return null;
	else return this.game.dom_movelist.querySelector("span.cs__ucomment[move_id='"+this.id+"']"); }
chess_spider.Move.prototype.dom_last = function() {
	if( this.ucomment )	return this.dom_ucomment();
	else if( this.comment )	return this.dom_comment();
	else			return this.dom_move();
}
//-chess_spider.Move.prototype.dom_div = function() { this.game.dom_movelist.selectorElement("[id='"+this.id+"']"); }
chess_spider.Move.prototype.lastMove = function() { for( var mv=this; mv.next(); mv=mv.next() ); 
		if( mv.branch_arr.length ) return mv.branch_arr[mv.branch_arr.length-1].lastMove(); else return mv; }
chess_spider.Move.prototype.dom_prevDiv = function() { return( (this.color=='w') ? 1 : 8 ); }
chess_spider.Move.prototype.backrank = function() { return( (this.color=='w') ? 1 : 8 ); }
chess_spider.Move.prototype.dom_table = function() {
	if( !this.dom_post ) return null;
	return this.dom_post.getElementsByTagName('table')[0];
}

/* decprecated: gc for move
chess_spider.Move.prototype.gc = function() {
	if( this.board && this.board.parentNode ) this.board.parentNode.removeChild( this.board );
	delete this.board;
	delete chess_spider.state.moves[this.id];
}
*/

